// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2013 Petar Maymounkov <p@gocircuit.org>

package ripple

import (
	"bytes"
	"circuit/kit/iomisc"
	"fmt"
	"math/rand"
	"path"
	"strings"
	"time"

	"circuit/kit/rh"
	"circuit/kit/rh/ns"
	"circuit/kit/rh/tokenlock"
)

func buildMutexHelp(name string, id uint64) string {
	return fmt.Sprintf(mutexFmt, path.Join("/", name), id)
}

const mutexFmt = `
  This is a mutex ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS

  cd cmd # Access command files for this slab.

  cat >> lock          # Open lock file to acquire the mutex lock and hold it.
                       # The open operation will block until the lock can be obtained.
                       # Once opened, the file will consume and discard all writes.
                       # The held lock is released on closing the open file descriptor.
  echo >> lock         # Open lock file to acquire the lock and release the lock right away.
  echo reset >> reset  # Reset the lock and abort all current waiters.
                       # Waiters and the lock holder, if any, will receive
                       # an error on next file operation.
  cat stat             # Read a description of the mutex status.
  cat help             # Read this help screen.

`

func NewMutex(name string) (*Mutex, rh.FID) {
	m := &Mutex{
		name:  name,
		gtm:   time.Now(),
		tlock: tokenlock.New(),
	}

	// lock FID
	m.lockFID = NewLock(m.tlock)

	// statFID
	m.statFID = ns.RegulateFork(func() ns.UnregulatedFID {
		nhold, nwait := m.tlock.Stat()
		body := fmt.Sprintf("NumHolding: %d\nNumWaiting: %d\n", nhold, nwait)
		r := bytes.NewReader([]byte(body))
		//
		return ns.NewReaderFID("mutex/stat", iomisc.ReaderNopCloser(r))
	})

	// helpFID
	m.helpFID = NewHelp(buildMutexHelp(name, uint64(rand.Int63())))

	// resetFID
	m.resetFID = NewEndpoint(func(line string) {
		if strings.TrimSpace(string(line)) != "reset" {
			return
		}
		m.tlock.Reset()
	})

	return m, ns.NewFID("mutex", &mutexDir{m})
}

// ▒▒ Mutex
type Mutex struct {
	name string
	gtm  time.Time
	//
	tlock    tokenlock.Lock
	lockFID  rh.FID
	statFID  rh.FID
	helpFID  rh.FID
	resetFID rh.FID
}

// mutexDir is an ns.InterfaceDir
type mutexDir struct {
	m *Mutex
}

func (md *mutexDir) Lock() rh.FID {
	return md.m.lockFID
}

func (md *mutexDir) Reset() rh.FID {
	return md.m.resetFID
}

func (md *mutexDir) Stat() rh.FID {
	return md.m.statFID
}

func (md *mutexDir) Help() rh.FID {
	return md.m.helpFID
}
